package edu.nwpu.zya.coreops.modules.job.task.alert;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import edu.nwpu.zya.coreops.common.utils.Constant;
import edu.nwpu.zya.coreops.common.utils.DateUtils;
import edu.nwpu.zya.coreops.config.ElasticSearchConfig;
import edu.nwpu.zya.coreops.modules.alert.entity.HistoryEntity;
import edu.nwpu.zya.coreops.modules.alert.entity.RuleEntity;
import edu.nwpu.zya.coreops.modules.alert.service.HistoryService;
import edu.nwpu.zya.coreops.modules.alert.service.RuleService;
import edu.nwpu.zya.coreops.modules.alert.vo.RuleVo;
import edu.nwpu.zya.coreops.modules.job.task.ITask;
import edu.nwpu.zya.coreops.modules.mail.entity.MailEntity;
import edu.nwpu.zya.coreops.modules.mail.service.MailService;
import edu.nwpu.zya.coreops.modules.sys.entity.SysUserEntity;
import edu.nwpu.zya.coreops.modules.sys.service.SysUserService;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;
import org.elasticsearch.search.aggregations.bucket.histogram.ParsedDateHistogram;
import org.elasticsearch.search.aggregations.metrics.Avg;
import org.elasticsearch.search.aggregations.metrics.Max;
import org.elasticsearch.search.aggregations.metrics.Min;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.io.Console;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 添加告警规则后，对该告警规则进行定时监控
 * 每分钟执行一次
 * Cron:0 0/1 * * * ?
 * params: 页面传过来的告警规则VO 主要使用告警规则描述，实例id
 */
@Component("alertTask")
public class AlertTask implements ITask {
    private Logger logger = LoggerFactory.getLogger(getClass());
    @Qualifier("esRestClient")
    @Autowired
    private RestHighLevelClient esRestClient;

    @Autowired
    private RuleService ruleService;

    @Autowired
    private HistoryService historyService;

    @Autowired
    private SysUserService sysUserService;

    @Autowired
    private MailService mailService;

    @Transactional
    @Override
    public void run(String params) {
        RuleVo ruleVo = JSONObject.parseObject(params, RuleVo.class);
        // 根据告警规则信息进行聚合查询
        SearchResponse searchResponse = getAggQueryResponse(ruleVo);
        List<Double> dataList = new ArrayList<>();
        
        if (searchResponse != null) {
            // 获取响应后，提取内容
            Aggregations aggregations = searchResponse.getAggregations();
            ParsedDateHistogram dateHistogram = aggregations.get(Constant.ESAggName.MINUTE_INTERVAL.getValue());
            dateHistogram.getBuckets().forEach(bucket -> {
                Double data = null;
                if (ruleVo.getAggType() == Constant.AggType.AVG.getValue()) {
                    Avg avg = bucket.getAggregations().get(ruleVo.getMetricName());
                    data = avg.getValue();
                    logger.debug("当前{}为:{}",Constant.AggType.AVG.getMsg(),data);
                } else if (ruleVo.getAggType() == Constant.AggType.MIN.getValue()) {
                    Min min = bucket.getAggregations().get(ruleVo.getMetricName());
                    data = min.getValue();
                    logger.debug("当前{}为:{}",Constant.AggType.MIN.getMsg(),data);
                } else {
                    Max max = bucket.getAggregations().get(ruleVo.getMetricName());
                    data = max.getValue();
                    logger.debug("当前{}为:{}",Constant.AggType.MAX.getMsg(),data);
                }
                dataList.add(data);

            });
        }
        // 获取上一次的状态
        Integer lastStatus = ruleService.getById(ruleVo.getId()).getStatus();
        if (isAlert(dataList, ruleVo)) {

            if (lastStatus == Constant.AlertStatus.NORMAL.getValue()) {
                // 如果超过阈值，且上次状态正常，则触发告警
                ruleService.update(new UpdateWrapper<RuleEntity>().eq("id", ruleVo.getId()).set("status", Constant.AlertStatus.ALERTING.getValue()));
                // 添加告警历史
                addRuleHistory(ruleVo,true);
                // 发送邮件
                sendEmail(true,ruleVo,dataList);

            }
        } else {
            if (lastStatus == Constant.AlertStatus.ALERTING.getValue()) {
                // 如果没超过阈值，且上次为告警状态，则将告警恢复正常
                ruleService.update(new UpdateWrapper<RuleEntity>().eq("id", ruleVo.getId()).set("status", Constant.AlertStatus.NORMAL.getValue()));
                // 添加告警历史
                addRuleHistory(ruleVo,false);
                // 发送邮件操作
                sendEmail(false,ruleVo,dataList);
            }
        }
        logger.debug("定时告警任务执行成功，告警id：{}，实例id：{}", ruleVo.getId(), ruleVo.getInstanceId());
    }

    /**
     * 添加告警历史
     * @param ruleVo
     * @param isAlert 是否触发告警，否则为恢复告警
     */
    private void addRuleHistory(RuleVo ruleVo,boolean isAlert) {
        HistoryEntity historyEntity = new HistoryEntity();
        historyEntity.setAlertTime(new Date());
        BeanUtils.copyProperties(ruleVo, historyEntity);
        historyEntity.setId(null);
        historyEntity.setRuleId(ruleVo.getId());
        historyEntity.setStatus(isAlert ? Constant.AlertHistoryStatus.OCCUR.getValue() : Constant.AlertHistoryStatus.RECOVERY.getValue());
        historyService.save(historyEntity);
    }

    /**
     * 发送邮件
     * @param isAlert 是否触发告警，否则为恢复告警
     * @param ruleVo
     * @param dataList
     */
    private void sendEmail(boolean isAlert,RuleVo ruleVo,List<Double> dataList){
        SysUserEntity sysUserEntity = sysUserService.getById(ruleVo.getUserId());
        MailEntity mailEntity = new MailEntity();
        mailEntity.setTo(sysUserEntity.getEmail());
        String instanceTypeStr="";
        if(ruleVo.getType()== Constant.InstanceType.ECS.getValue()){
            instanceTypeStr="ECS";
        }else if(ruleVo.getType()==Constant.InstanceType.RDS.getValue()){
            instanceTypeStr="RDS";
        }else {
            instanceTypeStr="APP";
        }
        mailEntity.setSubject(instanceTypeStr+"实例"+ruleVo.getInstanceId()+(isAlert?"发生告警":"告警恢复正常")+"，告警规则id："+ruleVo.getId());
        String mailText="最近"+ruleVo.getPeriod()+"个周期内"+ruleVo.getMetricName()+"的值为:\n";
        for (Double data :
                dataList) {
            mailText+=data+"\n";
        }
        mailEntity.setText(mailText);
        mailService.sendMail(mailEntity);

    }

    /**
     * 对数据进行比较，看是否超出阈值，触发告警
     * @param dataList 数据
     * @return
     */
    private Boolean isAlert(List<Double> dataList,RuleVo ruleVo) {
        Boolean flag=true;
        for (Double data :
                dataList) {
            if(ruleVo.getCompareType().equalsIgnoreCase(Constant.CompareType.EQ.getValue())){
                if(data.compareTo(Double.parseDouble(ruleVo.getThreshold()))!=0){
                    flag=false;
                }
            }else if(ruleVo.getCompareType().equalsIgnoreCase(Constant.CompareType.GT.getValue())){
                if(data.compareTo(Double.parseDouble(ruleVo.getThreshold()))<=0){
                    flag=false;
                }
            }else if(ruleVo.getCompareType().equalsIgnoreCase(Constant.CompareType.GTE.getValue())){
                if(data.compareTo(Double.parseDouble(ruleVo.getThreshold()))<0){
                    flag=false;
                }
            }else if(ruleVo.getCompareType().equalsIgnoreCase(Constant.CompareType.LT.getValue())){
                if(data.compareTo(Double.parseDouble(ruleVo.getThreshold()))>=0){
                    flag=false;
                }
            }else if(ruleVo.getCompareType().equalsIgnoreCase(Constant.CompareType.LTE.getValue())){
                if(data.compareTo(Double.parseDouble(ruleVo.getThreshold()))>0){
                    flag=false;
                }
            }else if(ruleVo.getCompareType().equalsIgnoreCase(Constant.CompareType.NE.getValue())){
                if(data.compareTo(Double.parseDouble(ruleVo.getThreshold()))==0){
                    flag=false;
                }
            }
        }
        return flag;
    }

    /**
     * 根据告警规则描述和实例id进行聚合查询
     * @return 查询结果响应
     */
    private SearchResponse getAggQueryResponse(RuleVo ruleVo){
        Date now = new Date();
        Date from = DateUtils.addDateMinutes(now, 0 - ruleVo.getPeriod());
        SearchRequest searchRequest = null;
        // 根据类型查询不同索引
        if (ruleVo.getType() == Constant.InstanceType.ECS.getValue()) {
            searchRequest = new SearchRequest("ecs");
        } else if (ruleVo.getType() == Constant.InstanceType.RDS.getValue()) {
            searchRequest = new SearchRequest("rds");
        } else {
            searchRequest = new SearchRequest("app");
        }
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(QueryBuilders.boolQuery().filter(QueryBuilders.rangeQuery("time").gte(from).lte(now)).must(QueryBuilders.matchQuery("instanceId", ruleVo.getInstanceId())));
        sourceBuilder.size(0);
        sourceBuilder.sort("time", SortOrder.ASC);

        // 按1分钟间隔进行聚合
        DateHistogramAggregationBuilder aggregationBuilder = AggregationBuilders.dateHistogram(Constant.ESAggName.MINUTE_INTERVAL.getValue()).field("time").fixedInterval(DateHistogramInterval.minutes(1));
        // 对于每个间隔，进行平均，最大，最小的操作之一
        if (ruleVo.getAggType() == Constant.AggType.AVG.getValue()) {
            aggregationBuilder.subAggregation(AggregationBuilders.avg(ruleVo.getMetricName()).field(ruleVo.getMetricName()));
        } else if (ruleVo.getAggType() == Constant.AggType.MIN.getValue()) {
            aggregationBuilder.subAggregation(AggregationBuilders.min(ruleVo.getMetricName()).field(ruleVo.getMetricName()));
        } else {
            aggregationBuilder.subAggregation(AggregationBuilders.max(ruleVo.getMetricName()).field(ruleVo.getMetricName()));
        }
        sourceBuilder.aggregation(aggregationBuilder);
        sourceBuilder.sort("time", SortOrder.ASC);

        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = null;
        try {
            searchResponse = esRestClient.search(searchRequest, ElasticSearchConfig.COMMON_OPTIONS);
        } catch (IOException e) {
            logger.error("定时告警任务执行失败，告警id：{}，实例id：{}", ruleVo.getId(), ruleVo.getInstanceId());
            e.printStackTrace();
        }

        return searchResponse;
    }
}
